<?php
namespace DRY\Module;

/**
 * DonReY Framework 2012 :: Data organization mechanism ( for databases )
 * Allows creating typical computer technology data structures: List, Tree, Graph, Hash in a table-based database system
 * Principle of operation is plugging itself into Query Factory objects, and providing a filter and order for queries.
 * Typical structure table contains an "item_id", a "structure_id" and various other fields which define the organization of the structure itself
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/structure.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Structure
 */
class Structure extends base
{
	// Exception/Error codes
	const
		EXC_STRUCTURE_TYPE_NOT_DEFINED = 1
		;

	const
		Autoload_Directory = 'structure',
		Autoload_Prefix = 'DRY\Structure\Type',
		Autoload_Suffix = 'structure'
		;

	/**
	 * Creates a structure object (and optionally initialize it's data - for trees for example, it creates the "root")
	 *
	 * @param string $type Type of structure: "list", "tree", "graph", "hash"
	 * @param int $id Structure's Id
	 * @param mixed $init_data Initial data for the structure ( ex: lists or hashes might be fully created with this data, a tree could create it's root ) - This is passed on to the structure constructor )
	 * @param string $struct_class For extensibility to other derived data structures
	 * @return \DRY\Structure\base The structure object
	 */
	public function factory_Structure($type, $id = 0, $init_data = null, $struct_class = null, $use_observers = false)
	{
		// check if type is defined in config
		if(!isset($this-> CFG-> types->$type))
			throw new \DRY\Water('structure', self::EXC_STRUCTURE_TYPE_NOT_DEFINED, array('type'=>$type));

		if(is_null($struct_class))
			$struct_class = self::Autoload_Prefix.'_'.$type;

		return new $struct_class($this, $this-> CFG-> types->$type, $id, $init_data, $use_observers);
	}

	/**
	 * Discovers a structure ID which is not yet used
	 *
	 * @param string $type
	 * @param string $struct_class For extensibility to other derived data structures
	 */
	public function find_Free_Structure_Id($type, $struct_class = null)
	{
		if(is_null($struct_class))
			$struct_class = self::Autoload_Prefix.'_'.$type;

		return $struct_class::find_Free_Id($this, $this-> CFG-> types->$type);
	}
}

namespace DRY\Structure;

/**
 * DonReY Framework 2012 :: Base class for data structures
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/structure.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Structure
 */
abstract class base implements \SplSubject
{
	/**
	 * Structure id, used in all database operations
	 * @var int
	 */
	protected $_id;

	/**
	 * Reference to the structure Module which instanced this object
	 * @var Module_Database
	 */
	protected $_structure;

	/**
	 * Primary table in the database that holds structure data (specific structures might have additional tables)
	 * @var string
	 */
	protected $_table;

	/**
	 * Constructor which creates the initial data for the structure handler object
	 * @param \DRY\Module\Structure $structure_module
	 * @param \SimpleXMLElement $CFG
	 * @param int $id
	 * @param mixed $init_data
	 */
	public function __construct(\DRY\Module\Structure $structure_module, $CFG, $id, $init_data = null, $use_observers = false)
	{
		$this->_id = $id;
		$this->_structure = &$structure_module;
		$this->_table = (string)$CFG-> storage['table'];

		if($use_observers)
			$this->_observers=new \SplObjectStorage();
	}

	/**
	 * Scans the structure's primary table for a structure_id which is not yet in use
	 *
	 * @param \DRY\Module\Structure $structure_module
	 * @param unknown_type $CFG
	 */
	public static function find_Free_Id(\DRY\Module\Structure $structure_module, $CFG)
	{
		$q = $structure_module['queryfactory']-> select()
			-> table((string)$CFG-> storage['table'], 'structure_id')
			-> order('structure_id', "DESC")
			-> limit(1);

		$row = $structure_module['connection']-> query($q)-> current();
		if($row)
			// largest + 1 will always be free
			return $row['structure_id'] + 1;
		else
			// when the table is empty
			return 1;
	}

	/**
	 * Removes the structure and all it's nodes
	 */
	abstract function destroy();

	protected $joinmode = false;

	/**
	 * Sets the mode of operation for all reading methods
	 * @param bool $readmode True = Work with joins, return a QueryFactory object; False = Work directly (execute query), return a database result or another data container (array, object)
	 */
	public function set_JoinMode($joinmode = true)
	{
		$this-> joinmode = $joinmode;
	}

	//===== Implementation of SplSubject ( Observer pattern ) =====/

	/**
	 * Observers currently attached to this structure
	 *
	 * @var SplObjectStorage
	 */
	protected $_observers;

	public function attach(SplObserver $observer)
	{
		if($this->_observers instanceof \SplObjectStorage)
			$this->_observers-> attach($observer);
	}

	public function detach(SplObserver $observer)
	{
		if($this->_observers instanceof \SplObjectStorage)
			$this->_observers-> detach($observer);
	}

	/**
	 * Sends a notification to all observers (so structure change operations can tell any potiential observer that something has changed)
	 */
	public function notify()
	{
		if($this->_observers instanceof \SplObjectStorage) {
			$event_type = func_get_arg(0);
			// $event_data depends on what event_type is
			$event_data = func_get_arg(1);
			foreach($this->_observers as $observer)
				$observer-> update($this, $event_type, $event_data);
		}
	}

}